﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of RinexFileReader.                                       **
**                                                                              **
**  RinexFileReader is free software: you can redistribute it and/or modify     **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  RinexFileReader is distributed in the hope that it will be useful,          **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with RinexFileReader. If not, see <https://www.gnu.org/licenses/>.    **
**********************************************************************************/
#pragma once

#include "CommonPEG.h"

#include <print>
#include <ranges>

namespace Rinex
{
struct ObservationData
{
    // IONEX VERSION / TYPE
    double version;
    SatelliteSystem system;
    // PGM / RUN BY / DATE
    std::string program;
    std::string agency;
    std::string createDateTime;
    // COMMENT
    std::vector<std::string> comments;
    // MARKER NAME
    std::string markerName;
    // *MARKER NUMBER
    std::optional<std::string> markerNumber;
    // MARKER TYPE
    std::string markerType;
    // OBSERVER / AGENCY
    std::tuple<std::string, std::string> observerAgency;
    // REC # / TYPE / VERS
    std::string recNumber;
    std::string recType;
    std::string recVersion;
    // ANT # / TYPE
    std::string antNumber;
    std::string antType;
    // APPROX POSITION XYZ
    std::tuple<double, double, double> approxPositionXYZ;
    // ANTENNA: DELTA H/E/N
    std::tuple<double, double, double> antennaDeltaHEN;
    // *ANTENNA: DELTA X/Y/Z
    std::optional<std::tuple<double, double, double>> antennaDeltaXYZ;
    // *ANTENNA: PHASECENTER
    std::optional<PhaseCenter> phaseCenter;
    // *ANTENNA: B.SIGHT XYZ
    std::optional<std::tuple<double, double, double>> antennaBSightXYZ;
    // *ANTENNA: ZERODIR AZI
    std::optional<double> antennaZerodirAZI;
    // *ANTENNA: ZERODIR XYZ
    std::optional<std::tuple<double, double, double>> antennaZerodirXYZ;
    // *CENTER OF MASS: XYZ
    std::optional<std::tuple<double, double, double>> centerOfMassXYZ;
    // *DOI
    std::optional<std::string> doi;
    // *LICENSE OF USE
    std::optional<std::string> licenseOfUse;
    // *STATION INFORMATION
    std::optional<std::string> stationInformation;
    // SYS / # / OBS TYPES
    std::vector<SysObsTypes> sysObsTypes;
    // *SIGNAL STRENGTH UNIT
    std::optional<std::string> signalStrengthUnit;
    // *INTERVAL
    std::optional<double> interval;
    // TIME OF FIRST OBS
    TimeOfObs timeOfFirstObs;
    // *TIME OF LAST OBS
    std::optional<TimeOfObs> timeOfLastObs;
    // *RCV CLOCK OFFS APPL
    // *SYS / PCVS APPLIED
    // *SYS / SCALE FACTOR
    // *SYS / PHASE SHIFT
    // GLONASS SLOT / FRQ #
    std::vector<GLONASSSlotFreq> glonassSlotFreq;
    // *GLONASS COD/PHS/BIS
    // *LEAP SECONDS
    std::optional<LeapSeconds> leapSeconds;
    // *# OF SATELLITES
    std::optional<int> ofSatellites;
    // *PRN / # OF OBS
    std::vector<PrnOfObs> prnOfObs;
    ////////////////////////////////
    std::vector<ObsData> datas;

    inline int typeCount(SatelliteSystem sys) const
    {
        if (auto it = std::ranges::find_if(sysObsTypes, [&sys](const SysObsTypes& ts) { return ts.satelliteSystem == sys; });
            it != sysObsTypes.end())
            return static_cast<int>(it->types.size());
        else
            return 0;
    }
};

namespace PEG
{
namespace Observation
{
using namespace tao::pegtl;
// clang-format off
struct Version : Float {};
struct Type : one<'O'> {};
struct Satellite : one<'G', 'R', 'E', 'J', 'C', 'I', 'S', 'M'> {};

struct RINEX_VERSION_TYPE : seq<Space, Version, rep<11, ascii::any>, Type, rep<19, ascii::any>, Satellite, rep<19, ascii::any>, RINEX_KEYWORDS("RINEX VERSION / TYPE")> {};
struct PGM_RUNBY_DATE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("PGM / RUN BY / DATE")> {};
struct COMMENT : seq<rep<60, ascii::any>, RINEX_KEYWORDS("COMMENT")> {};
struct MARKER_NAME : seq<rep<60, ascii::any>, RINEX_KEYWORDS("MARKER NAME")> {};
struct MARKER_NUMBER : seq<rep<60, ascii::any>, RINEX_KEYWORDS("MARKER NUMBER")> {};
struct MARKER_TYPE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("MARKER TYPE")> {};
struct OBSERVER_AGENCY : seq<rep<60, ascii::any>, RINEX_KEYWORDS("OBSERVER / AGENCY")> {};
struct ANT_TYPE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("ANT # / TYPE")> {};
struct REC_TYPE_VERS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("REC # / TYPE / VERS")> {};
struct APPROX_POSITION_XYZ : seq<rep<60, ascii::any>, RINEX_KEYWORDS("APPROX POSITION XYZ")> {};
struct ANTENNA_DELTA_H_E_N : seq<rep<60, ascii::any>, RINEX_KEYWORDS("ANTENNA: DELTA H/E/N")> {};
struct ANTENNA_DELTA_X_Y_Z : seq<rep<60, ascii::any>, RINEX_KEYWORDS("ANTENNA: DELTA X/Y/Z")> {};
struct ANTENNA_PHASECENTER : seq<rep<60, ascii::any>, RINEX_KEYWORDS("ANTENNA: PHASECENTER")> {};
struct ANTENNA_B_SIGHT_XYZ : seq<rep<60, ascii::any>, RINEX_KEYWORDS("ANTENNA: B.SIGHT XYZ")> {};
struct ANTENNA_ZERODIR_AZI : seq<rep<60, ascii::any>, RINEX_KEYWORDS("ANTENNA: ZERODIR AZI")> {};
struct ANTENNA_ZERODIR_XYZ : seq<rep<60, ascii::any>, RINEX_KEYWORDS("ANTENNA: ZERODIR XYZ")> {};
struct CENTER_OF_MASS_XYZ : seq<rep<60, ascii::any>, RINEX_KEYWORDS("CENTER OF MASS: XYZ")> {};
struct DOI : seq<rep<60, ascii::any>, RINEX_KEYWORDS("DOI")> {};
struct LICENSE_OF_USE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("LICENSE OF USE")> {};
struct STATION_INFORMATION : seq<rep<60, ascii::any>, RINEX_KEYWORDS("STATION INFORMATION")> {};
struct SYS_OBS_TYPES : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SYS / # / OBS TYPES")> {};
struct SIGNAL_STRENGTH_UNIT : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SIGNAL STRENGTH UNIT")> {};
struct INTERVAL : seq<rep<60, ascii::any>, RINEX_KEYWORDS("INTERVAL")> {};
struct TIME_OF_FIRST_OBS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("TIME OF FIRST OBS")> {};
struct TIME_OF_LAST_OBS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("TIME OF LAST OBS")> {};
struct RCV_CLOCK_OFFS_APPL : seq<rep<60, ascii::any>, RINEX_KEYWORDS("RCV CLOCK OFFS APPL")> {};
struct SYS_DCBS_APPLIED : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SYS / DCBS APPLIED")> {};
struct SYS_PCVS_APPLIED : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SYS / PCVS APPLIED")> {};
struct SYS_SCALE_FACTOR : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SYS / SCALE FACTOR")> {};
struct SYS_PHASE_SHIFT : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SYS / PHASE SHIFT")> {};
struct GLONASS_SLOT_FRQ : seq<rep<60, ascii::any>, RINEX_KEYWORDS("GLONASS SLOT / FRQ #")> {};
struct GLONASS_COD_PHS_BIS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("GLONASS COD/PHS/BIS")> {};
struct LEAP_SECONDS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("LEAP SECONDS")> {};
struct OF_SATELLITES : seq<rep<60, ascii::any>, RINEX_KEYWORDS("# OF SATELLITES")> {};
struct PRN_OF_OBS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("PRN / # OF OBS")> {};
struct WAVELENGTH_FACT_L1_2 : seq<rep<60, ascii::any>, RINEX_KEYWORDS("WAVELENGTH FACT L1/2")> {};
struct HeaderEnd : until<RINEX_KEYWORDS("END OF HEADER")> {};

struct OBS_DATA {};
// clang-format on

struct HeaderLine
    : sor<PGM_RUNBY_DATE,
          COMMENT,
          MARKER_NAME,
          MARKER_NUMBER,
          MARKER_TYPE,
          OBSERVER_AGENCY,
          ANT_TYPE,
          REC_TYPE_VERS,
          APPROX_POSITION_XYZ,
          ANTENNA_DELTA_H_E_N,
          ANTENNA_DELTA_X_Y_Z,
          ANTENNA_PHASECENTER,
          ANTENNA_B_SIGHT_XYZ,
          ANTENNA_ZERODIR_AZI,
          ANTENNA_ZERODIR_XYZ,
          CENTER_OF_MASS_XYZ,
          DOI,
          LICENSE_OF_USE,
          STATION_INFORMATION,
          SYS_OBS_TYPES,
          SIGNAL_STRENGTH_UNIT,
          INTERVAL,
          TIME_OF_FIRST_OBS,
          TIME_OF_LAST_OBS,
          RCV_CLOCK_OFFS_APPL,
          SYS_DCBS_APPLIED,
          SYS_PCVS_APPLIED,
          SYS_SCALE_FACTOR,
          SYS_PHASE_SHIFT,
          GLONASS_SLOT_FRQ,
          GLONASS_COD_PHS_BIS,
          LEAP_SECONDS,
          OF_SATELLITES,
          PRN_OF_OBS,
          WAVELENGTH_FACT_L1_2>
{
};

struct Header : seq<RINEX_VERSION_TYPE, plus<HeaderLine>, until<HeaderEnd>>
{
};

using Rule = seq<Header, plus<OBS_DATA>, eof>;

template<typename T>
struct Action
{
};

template<>
struct Action<Version> : DoubleHelper<struct Action<Version>>
{
    static void setValue(ObservationData& data, double value) { data.version = value; }
};

template<>
struct Action<Satellite>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.system = toSatelliteSystem(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<PGM_RUNBY_DATE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.program        = trim(str.substr(0, 20));
        data.agency         = trim(str.substr(20, 20));
        data.createDateTime = trim(str.substr(40, 20));
    }
};

template<>
struct Action<COMMENT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.comments.push_back(std::string(trim(std::string_view(input.begin(), input.begin() + 60))));
    }
};

template<>
struct Action<MARKER_NAME>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.markerName = trim(std::string_view(input.begin(), input.end()).substr(0, 60)); // A60
    }
};

template<>
struct Action<MARKER_NUMBER>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.markerNumber = trim(std::string_view(input.begin(), input.end()).substr(0, 20)); // A20
    }
};

template<>
struct Action<MARKER_TYPE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.markerType = trim(std::string_view(input.begin(), input.end()).substr(0, 20)); // A20
    }
};

template<>
struct Action<OBSERVER_AGENCY>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        std::get<0>(data.observerAgency) = trim(str.substr(0, 20));  // A20
        std::get<1>(data.observerAgency) = trim(str.substr(20, 40)); // A40
    }
};

template<>
struct Action<ANT_TYPE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.antNumber = trim(str.substr(0, 20));  // A20
        data.antType   = trim(str.substr(20, 20)); // A20
    }
};

template<>
struct Action<REC_TYPE_VERS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.recNumber  = trim(str.substr(0, 20));  // A20
        data.recType    = trim(str.substr(20, 20)); // A20
        data.recVersion = trim(str.substr(40, 20)); // A20
    }
};

template<>
struct Action<APPROX_POSITION_XYZ>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.approxPositionXYZ =
            std::make_tuple(stod(trim(str.substr(0, 14))), stod(trim(str.substr(14, 14))), stod(trim(str.substr(28, 14)))); // 3F14.4
    }
};

template<>
struct Action<ANTENNA_DELTA_H_E_N>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.antennaDeltaHEN =
            std::make_tuple(stod(trim(str.substr(0, 14))), stod(trim(str.substr(14, 14))), stod(trim(str.substr(28, 14)))); // 3F14.4
    }
};

template<>
struct Action<ANTENNA_DELTA_X_Y_Z>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.antennaDeltaXYZ =
            std::make_tuple(stod(trim(str.substr(0, 14))), stod(trim(str.substr(14, 14))), stod(trim(str.substr(28, 14)))); // 3F14.4
    }
};

template<>
struct Action<ANTENNA_PHASECENTER>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.phaseCenter                  = PhaseCenter {};
        data.phaseCenter->satelliteSystem = toSatelliteSystem(str.substr(0, 1)); // A1
        data.phaseCenter->observationCode = trim(str.substr(2, 3));              // 1X, A3
        data.phaseCenter->northEastUpOrXYZ =
            std::make_tuple(stod(trim(str.substr(5, 9))), stod(trim(str.substr(14, 14))), stod(trim(str.substr(28, 14)))); // F9.4,2F14.4
    }
};

template<>
struct Action<ANTENNA_B_SIGHT_XYZ>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.antennaBSightXYZ =
            std::make_tuple(stod(trim(str.substr(0, 14))), stod(trim(str.substr(14, 14))), stod(trim(str.substr(28, 14)))); // 3F14.4
    }
};

template<>
struct Action<ANTENNA_ZERODIR_AZI>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.antennaZerodirAZI = stod(trim(str.substr(0, 14))); // F14.4
    }
};

template<>
struct Action<ANTENNA_ZERODIR_XYZ>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.antennaBSightXYZ =
            std::make_tuple(stod(trim(str.substr(0, 14))), stod(trim(str.substr(14, 14))), stod(trim(str.substr(28, 14)))); // 3F14.4
    }
};

template<>
struct Action<CENTER_OF_MASS_XYZ>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.centerOfMassXYZ =
            std::make_tuple(stod(trim(str.substr(0, 14))), stod(trim(str.substr(14, 14))), stod(trim(str.substr(28, 14)))); // 3F14.4
    }
};

template<>
struct Action<DOI>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.doi = trim(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<LICENSE_OF_USE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.licenseOfUse = trim(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<STATION_INFORMATION>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        data.stationInformation = trim(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<SYS_OBS_TYPES>
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    [[nodiscard]] static bool match(ParseInput& in, ObservationData& data, States&&... st)
    {
        auto line = in.line_at(in.position());
        if (!line.contains("SYS / # / OBS TYPES"))
            return false;
        SysObsTypes sys;
        sys.satelliteSystem = toSatelliteSystem(line.substr(0, 1));
        int types           = stoi(trim(line.substr(3, 3)));
        in.bump(line.size());
        consumeTail(in);

        auto parseTypes = [&sys](std::string_view source)
        {
            for (auto i : std::views::iota(0, 13))
            {
                auto str = trim(source.substr(6 + i * 4, 4));
                if (str.empty())
                    return;
                sys.types.push_back(std::string(str));
            }
        };
        parseTypes(line);

        if (types > 13)
        {
            auto secLine = in.line_at(in.position());
            parseTypes(secLine);
            in.bump(secLine.size());
            consumeTail(in);
        }
        data.sysObsTypes.push_back(sys);
        return true;
    }
};

template<>
struct Action<SIGNAL_STRENGTH_UNIT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.signalStrengthUnit = trim(str.substr(0, 20)); // A20
    }
};

template<>
struct Action<INTERVAL>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.interval = stod(trim(str.substr(0, 10))); // F10.3
    }
};

inline TimeOfObs parseTimeOfObs(std::string_view str)
{
    TimeOfObs obs;
    int offset     = 0;
    auto getOffset = [&offset](int size)
    {
        auto r = offset;
        offset += size;
        return r;
    };
    obs.time.year   = stoi(trim(str.substr(getOffset(6), 6)));   // I6
    obs.time.month  = stoi(trim(str.substr(getOffset(6), 6)));   // I6
    obs.time.day    = stoi(trim(str.substr(getOffset(6), 6)));   // I6
    obs.time.hour   = stoi(trim(str.substr(getOffset(6), 6)));   // I6
    obs.time.minute = stoi(trim(str.substr(getOffset(6), 6)));   // I6
    double sec      = stod(trim(str.substr(getOffset(13), 13))); // F13.7
    obs.time.second = static_cast<int>(sec);
    obs.time.msec   = static_cast<int>((sec - obs.time.second) * 1000);
    getOffset(5);                                                               // 5X
    obs.systemTime = toSystemTimeIdentifier(trim(str.substr(getOffset(3), 3))); // A3
    return obs;
}

template<>
struct Action<TIME_OF_FIRST_OBS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.timeOfFirstObs = parseTimeOfObs(str);
    }
};

template<>
struct Action<TIME_OF_LAST_OBS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, ObservationData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.timeOfLastObs = parseTimeOfObs(str);
    }
};

template<>
struct Action<GLONASS_SLOT_FRQ>
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    [[nodiscard]] static bool match(ParseInput& in, ObservationData& data, States&&... st)
    {
        auto line = in.line_at(in.position());
        if (!line.contains("GLONASS SLOT / FRQ #"))
            return false;
        for (auto count = stoi(trim(line.substr(0, 3))); count > 0;)
        {
            line = in.line_at(in.position());
            if (!line.contains("GLONASS SLOT / FRQ #"))
                return false;

            for (auto view : line.substr(4, 56) | std::views::chunk(7)) // A1,I2.2,1X,I2,1X
            {
                std::string_view v(view.begin(), view.end());
                GLONASSSlotFreq gsf;
                gsf.systemCode      = v.at(0);
                gsf.slot_           = stoi(trim(v.substr(1, 2)));
                gsf.frequencyNumber = stoi(trim(v.substr(4, 2)));
                data.glonassSlotFreq.push_back(gsf);
                count--;
                if (count == 0)
                    break;
            }
            in.bump(line.size());
            consumeTail(in);
        }
        return true;
    }
};

template<>
struct Action<LEAP_SECONDS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& in, ObservationData& data)
    {
        std::string_view str(in.begin(), in.end());
        auto toInt = [&str](int offset, int size) -> std::optional<int>
        {
            auto s = trim(str.substr(offset, size));
            return s.empty() ? std::optional<int> {} : stoi(s);
        };
        LeapSeconds ls;
        ls.currentNumber       = toInt(0, 6);             // I6
        ls.deltaTLSF           = toInt(6, 6);             // I6
        ls.wnLSF               = toInt(12, 6);            // I6
        ls.respectiveDayNumber = toInt(18, 6);            // I6
        auto sys               = trim(str.substr(24, 3)); // A3
        if (sys.empty())
            ls.systemTimeIdentifier = SystemTimeIdentifier::GPS;
        else
            ls.systemTimeIdentifier = toSystemTimeIdentifier(sys);
        data.leapSeconds = ls;
    }
};

template<>
struct Action<OF_SATELLITES>
{
    template<typename ParseInput>
    static void apply(const ParseInput& in, ObservationData& data)
    {
        data.ofSatellites = stoi(trim(std::string_view(in.begin(), in.begin() + 6))); // I6
    }
};

template<>
struct Action<PRN_OF_OBS>
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    [[nodiscard]] static bool match(ParseInput& in, ObservationData& data, States&&... st)
    {
        auto line = in.line_at(in.position());
        if (!line.contains("PRN / # OF OBS"))
            return false;
        PrnOfObs poo;
        poo.satelliteSystem = toSatelliteSystem(line.substr(3, 1));
        poo.id              = stoi(trim(line.substr(4, 2)));
        int count           = data.typeCount(poo.satelliteSystem);
        for (; count > 0;)
        {
            line = in.line_at(in.position());
            if (!line.contains("PRN / # OF OBS"))
                return false;

            for (auto view : line.substr(6, 54) | std::views::chunk(6)) // 9I6
            {
                auto str = trim(std::string_view(view.begin(), view.end()));
                if (!str.empty())
                    poo.records.push_back(stoi(str));
                else
                    poo.records.push_back(std::nullopt);
                count--;
                if (count == 0)
                    break;
            }
            in.bump(line.size());
            consumeTail(in);
        }
        data.prnOfObs.push_back(std::move(poo));
        return true;
    }
};

template<>
struct Action<OBS_DATA>
{
    template<typename ParseInput>
    static std::vector<ObsRecords> parseObsRecords(ParseInput& in, int count, const ObservationData& data)
    {
        std::vector<ObsRecords> ors;
        for (auto _ : std::views::iota(0, count))
        {
            ObsRecords record;
            auto line              = in.line_at(in.position());
            record.satelliteSystem = toSatelliteSystem(line.substr(0, 1)); // A1
            record.id              = stoi(trim(line.substr(1, 2)));        // I2.2

            for (auto view : line | std::views::drop(3) | std::views::chunk(14 + 1 + 1)) // F14.3,I1,I1
            {
                std::string_view block(view.begin(), view.end());
                auto str = trim(block.substr(0, 14));
                if (!str.empty())
                {
                    ObsRecords::Record d;
                    d.record = stod(str);
                    if (block.size() >= 15 && block.at(14) != ' ')
                        d.lli = stoi(block.substr(14, 1));
                    if (block.size() >= 16 && block.at(15) != ' ')
                        d.ssi = stoi(block.substr(15, 1));
                    record.records.push_back(d);
                }
                else
                    record.records.push_back(std::nullopt);
            }
            record.records.resize(data.typeCount(record.satelliteSystem));
            ors.push_back(record);
            in.bump(line.size());
            consumeTail(in);
        }
        return ors;
    }
    template<typename ParseInput>
    static void parseObsSpecialRecords(ParseInput& in, int count) // 忽略
    {
        for (auto _ : std::views::iota(0, count))
        {
            auto line = in.line_at(in.position());
            in.bump(in.size());
            consumeTail(in);
        }
    }
    template<typename ParseInput>
    static void parseObsCycleSlipRecords(ParseInput& in, int count) // 忽略
    {
        for (auto _ : std::views::iota(0, count))
        {
            auto line = in.line_at(in.position());
            in.bump(in.size());
            consumeTail(in);
        }
    }
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    [[nodiscard]] static bool match(ParseInput& in, ObservationData& data, States&&... st)
    {
        auto line = in.line_at(in.position());
        if (!line.starts_with(">"))
            return false;
        // std::println("{}", line);
        ObsData obs;
        obs.epoch.year         = stoi(line.substr(2, 4));         // I4
        obs.epoch.month        = stoi(trim(line.substr(6, 3)));   // 1X,I2.2
        obs.epoch.day          = stoi(trim(line.substr(9, 3)));   // 1X,I2.2
        obs.epoch.hour         = stoi(trim(line.substr(12, 3)));  // 1X,I2.2
        obs.epoch.minute       = stoi(trim(line.substr(15, 3)));  // 1X,I2.2
        double sec             = stod(trim(line.substr(18, 11))); // F11.7
        obs.epoch.second       = static_cast<int>(sec);
        obs.epoch.msec         = static_cast<int>((sec - obs.epoch.second) * 1000);
        obs.epochFlag          = stoi(trim(line.substr(29, 3))); // 2X, I1
        obs.numberOfSatellites = stoi(trim(line.substr(32, 3))); //  I3
        if (line.size() >= 35 + 6 + 15)
        {
            obs.receiverClockOffsetCorrection = stod(trim(line.substr(41, 15))); // 6X, F15.12
        }
        in.bump(line.size());
        consumeTail(in);
        switch (obs.epochFlag)
        {
        case 0:
        case 1:
            obs.records = parseObsRecords(in, obs.numberOfSatellites, data);
            data.datas.push_back(obs); // 仅记录数据
            break;
        case 2:
        case 3:
        case 4:
        case 5:
            parseObsSpecialRecords(in, obs.numberOfSatellites);
            break;
        case 6:
            parseObsCycleSlipRecords(in, obs.numberOfSatellites);
            break;
        default:
            return false;
        }
        return true;
    }
};

} // namespace Observation
} // namespace PEG
} // namespace Rinex